/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.mongodb.domain.repository;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.util.Assert;

import com.foreveross.bsl.common.utils.enums.OperatEnum;
import com.foreveross.bsl.common.utils.web.QuerySpecs;
import com.foreveross.bsl.mongodb.repository.DynamicQueryBuilder;


/**
 * @description
 *
 * @author rening
 * @version v1.0
 *
 * @2013-6-24
 *
 */
public class EntityRepositoryImpl<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> implements EntityRepository<T,ID> {

	private MongoOperations mongoOperations;

	private final MongoEntityInformation<T, ID> entityInformation;
	
	private Class<T> clazz;
	
	/**
	 * @param metadata
	 * @param mongoOperations
	 */
	public EntityRepositoryImpl(MongoEntityInformation<T, ID> metadata,
			MongoOperations mongoOperations) {
		super(metadata, mongoOperations);
		entityInformation = metadata;
		this.mongoOperations = mongoOperations;
		clazz = metadata.getJavaType();
//		clazz = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findListByQuery(org.springframework.data.mongodb.core.query.Query)
	 */
	@Override
	public List<T> findListByQuery(Query query) {
		return mongoOperations.find(query, clazz);
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findListByProperty(java.lang.String, com.foreveross.common.infra.enums.OperatEnum, java.lang.Object)
	 */
	@Override
	public List<T> findListByProperty(String proName, OperatEnum operat,
			Object value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);

		List<T> list = mongoOperations.find(query, clazz);
		return list;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findListByProperty(java.lang.String[], com.foreveross.common.infra.enums.OperatEnum[], java.lang.Object[])
	 */
	@Override
	public List<T> findListByProperty(String[] proName, OperatEnum[] operat,
			Object[] value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);

		List<T> list = mongoOperations.find(query, clazz);
		return list;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findByQuery(org.springframework.data.mongodb.core.query.Query)
	 */
	@Override
	public Page<T> findByQuery(Query query) {
		int size = query.getLimit();
		if(size <= 0){
			throw new RuntimeException("page size is not init.");
		}
		int skip = query.getSkip();
		int page = skip / size;
		Pageable pageable = new PageRequest(page, size);
		
		long c = mongoOperations.count(query, clazz);
		List<T> list = mongoOperations.find(query, clazz);
		Page<T> p = new PageImpl<T>(list, pageable, c);
		return p;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findByQuery(org.springframework.data.mongodb.core.query.Query, org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findByQuery(Query query, Pageable pageable) {
		query.limit(pageable.getPageSize());
		query.skip(pageable.getOffset());
		query.with(pageable.getSort());
		
		long c = mongoOperations.count(query, clazz);
		List<T> list = mongoOperations.find(query, clazz);
		Page<T> p = new PageImpl<T>(list, pageable, c);
		return p;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findByProperty(java.lang.String, com.foreveross.common.infra.enums.OperatEnum, java.lang.Object, org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findByProperty(String proName, OperatEnum operat,
			Object value, Pageable pageable) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);
		query.limit(pageable.getPageSize());
		query.skip(pageable.getOffset());
		query.with(pageable.getSort());

		long c = mongoOperations.count(query, clazz);
		List<T> list = mongoOperations.find(query, clazz);
		Page<T> p = new PageImpl<T>(list, pageable, c);
		return p;
	}

	/**
	 * @param proName
	 * @param operat
	 * @param value
	 * @return
	 */
	private Criteria createCriteria(String proName, OperatEnum operat,
			Object value) {
		String pn = getDBFieldName(proName);
		Criteria criteria = Criteria.where(pn);
		switch (operat) {
		case GT:
			criteria.gt(value);
			break;
		case LT:
			criteria.lt(value);
			break;
		case GTE:
			criteria.gte(value);
			break;
		case LTE:
			criteria.lte(value);
			break;
		case EQ:
			criteria.is(value);
			break;
		case NE:
			criteria.ne(value);
			break;
		case IN:
			if(value instanceof Collection){
				criteria.in((Collection<?>)value);
			}
			else if(value.getClass().isArray()){
				Object[] array=(Object[])value;
				criteria.in(Arrays.asList(array));
			}
			else{
				criteria.in(value);
			}
			break;
		case NIN:
			if(value instanceof Collection){
				criteria.nin((Collection<?>)value);
			}
			else if(value.getClass().isArray()){
				Object[] array=(Object[])value;
				criteria.nin(Arrays.asList(array));
			}
			else{
				criteria.nin(value);
			}
			break;
		case NOT:
			criteria.not();
			break;
		case ALL:
			criteria.all(value);
			break;
		}
		return criteria;
	}

	/**
	 * @param proName
	 * @param pn
	 * @return
	 */
	private String getDBFieldName(String proName) {
		try {
			java.lang.reflect.Field field = clazz.getDeclaredField(proName);
			if(field != null){
				if(field.getAnnotation(Field.class) != null){
					return field.getAnnotation(Field.class).value();
				} else {
					return proName;
				}
			} else {
				throw new RuntimeException("the class is not declar the field.");
			}
		} catch (SecurityException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findByProperty(java.lang.String[], com.foreveross.common.infra.enums.OperatEnum[], java.lang.Object[], org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findByProperty(String[] proName, OperatEnum[] operat,
			Object[] value, Pageable pageable) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);
		query.limit(pageable.getPageSize());
		query.skip(pageable.getOffset());
		query.with(pageable.getSort());

		long c = mongoOperations.count(query, clazz);
		List<T> list = mongoOperations.find(query, clazz);
		Page<T> p = new PageImpl<T>(list, pageable, c);
		return p;
	}

	/**
	 * @param proName
	 * @param operat
	 * @param value
	 * @return
	 */
	private Criteria createCriteria(String[] proName, OperatEnum[] operat,
			Object[] value) {
		Criteria criteria = null;
		for (int i = 0; i < proName.length; i++) {
			if(i == 0){
				criteria = new Criteria(getDBFieldName(proName[i]));
			} else {
				criteria = criteria.and(getDBFieldName(proName[i]));
			}
			switch (operat[i]) {
			case GT:
				criteria.gt(value[i]);
				break;
			case LT:
				criteria.lt(value[i]);
				break;
			case GTE:
				criteria.gte(value[i]);
				break;
			case LTE:
				criteria.lte(value[i]);
				break;
			case EQ:
				criteria.is(value[i]);
				break;
			case NE:
				criteria.ne(value[i]);
				break;
			case IN:
				criteria.in(value[i]);
				break;
			case NIN:
				criteria.nin(value[i]);
				break;
			case NOT:
				criteria.not();
				break;
			case ALL:
				criteria.all(value[i]);
				break;
			}
		}
		return criteria;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findOneByProperty(java.lang.String, com.foreveross.common.infra.enums.OperatEnum, java.lang.Object)
	 */
	@Override
	public T findOneByProperty(String proName, OperatEnum operat, Object value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);

		List<T> list = mongoOperations.find(query, clazz);
		if(list != null && !list.isEmpty()){
			return list.get(0);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findOneByProperty(java.lang.String[], com.foreveross.common.infra.enums.OperatEnum[], java.lang.Object[])
	 */
	@Override
	public T findOneByProperty(String[] proName, OperatEnum[] operat,
			Object[] value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);

		List<T> list = mongoOperations.find(query, clazz);
		if(list != null && !list.isEmpty()){
			return list.get(0);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#deleteByProperty(java.lang.String, com.foreveross.common.infra.enums.OperatEnum, java.lang.Object)
	 */
	@Override
	public void deleteByProperty(String proName, OperatEnum operat, Object value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);

		mongoOperations.remove(query, clazz);
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#deleteByProperty(java.lang.String[], com.foreveross.common.infra.enums.OperatEnum[], java.lang.Object[])
	 */
	@Override
	public void deleteByProperty(String[] proName, OperatEnum[] operat,
			Object[] value) {
		Criteria criteria = createCriteria(proName, operat, value);
		Query query = new Query(criteria);
		
		mongoOperations.remove(query, clazz);
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.domain.repository.EntityRepository#findByQuerySpecs(com.foreveross.common.infra.web.QuerySpecs, org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findByQuerySpecs(QuerySpecs querySpecs, Pageable pageable) {
		Assert.notNull(querySpecs, "querySpecs can not be null");
		Query query=new DynamicQueryBuilder().byQuerySpecs(querySpecs, clazz);
		return this.findByQuery(query, pageable);
	}
}
